
package futbol.tacticas.concursantes.drakkar;


import futbol.tacticas.SituacionJugadores;
import java.awt.Point;




/** 
 * Informacion adicional sobre el estado del juego.
 * El Framework de JavaCup oculta datos que seria muy util conocer.
 * Esta clase intenta calcular/deducir estos datos, aunque no son 100% precisos.
 *
 * @author drakkar
 */
class Info {

    /** Frames Por Segundo. */
    public final int FPS  = 10;
    
    /** PERiodo en milisegundos */
    public final int PER  = 100;
    
    /* Ticks/Frames que dura el juego */
    public final int TICKS = 1800;

    /* Segundos que dura el juego */
    public final int SEGS = TICKS/FPS;
    
    
    
    /** Datos de un jugador. */
    public class Jugador {
        private int e = 0, n = 0, x = 0, y = 0, v = 0, f = 0, x0 = 0, y0 = 0;
        int getE() { return e; } // equipo
        int getN() { return n; } // numero
        int getX() { return x; } // pos x
        int getY() { return y; } // pos y
        int getF() { return f; } // fuerza
        int getV() { return v; } // velocidad
    }
    
    /** Datos de la bola. */
    public class Bola {
        private int v = 0;
        private int[][] xyd = new int[22][3];  // x, y, dist
        private double a = 0.0;
        private double dx = 0, dy = 0;
        private int q = 0;
        int getX() { return xyd[v][0]; }       // pos x
        int getY() { return xyd[v][1]; }       // pos y
        int getFinX() { return xyd[0][0]; }    // pos x final
        int getFinY() { return xyd[0][1]; }    // pos y final
        int getGolpeX() { return (14 > v ? xyd[v][0] : xyd[ 14][0] ); } // primera pos x golpeable
        int getGolpeY() { return (14 > v ? xyd[v][1] : xyd[ 14][1] ); } // primera pos y golpeable
        int getX(int f) { return ( f > v ? xyd[0][0] : xyd[v-f][0] ); } // pos x dentro de f frames/ticks
        int getY(int f) { return ( f > v ? xyd[0][1] : xyd[v-f][1] ); } // pos y dentro de f frames/ticks
        int getV()       { return  v; } // velocidad
        double getA()    { return  a; } // angulo
        double getQ()    { return  q; } // quieta
        double getDirX() { return dx; } // direccion en X
        double getDirY() { return dy; } // direccion en Y
        int    getDist(int f) { return ( f > v ? xyd[0][2] : xyd[v-f][2] ); } // distancia dentro de f frames/ticks
        double getErr (int f) { return    0.32*( f > v ? xyd[0][2] : xyd[v-f][2] ); } // error maximo dentro de f frames/ticks
        double getErrX(int f) { return dy*0.32*( f > v ? xyd[0][2] : xyd[v-f][2] ); } // error x dentro de f frames/ticks
        double getErrY(int f) { return dx*0.32*( f > v ? xyd[0][2] : xyd[v-f][2] ); } // error y dentro de f frames/ticks
    }
    
    
    private Jugador[][] jugs = new Jugador[2][11];
    private Bola bola = new Bola();
    private int[] puntos = new int[] {0, 0};
    private int tticks = -1;
    private int gticks = -1;
    private int gol = 0;
    private boolean golpe = false;
    private boolean migolpe = false;
    private double migolpeAng = 0.0;
    private double migolpeVel = 0.0;
    private long desempate = 0;
    private Jugador quien = null;

    
    /**
     * Constructor.
     * @param fuerzas  Array de fuerzas de los 11 jugadores de mi equipo.
     */
    public Info(int[] fuerzas) {
        for(int e = 0; e < 2; e++)
            for(int n = 0; n < 11; n++) {
                jugs[e][n] = new Jugador();
                jugs[e][n].e = e;
                jugs[e][n].n = n;
                if(e == 0) {
                    jugs[e][n].f = fuerzas[n];
                    jugs[e][n].v = 10-fuerzas[n];
                }
            }
    }
    
    
    /** 
     * Numero de ticks/frames transcurridos desde el inicio.
     * @return Ticks/Frames transcurridos.
     */
    public int getTotalTicks() {
        return tticks;
    }
    
    /** 
     * Numero de ticks/frames transcurridos desde el ultimo gol.
     * @return Ticks/Frames desde ultimo gol.
     */
    public int getGolTicks() {
        return gticks;
    }
    
    /** 
     * Numero de segundos desde el inicio del partido.
     * @return Segundos pasados.
     */
    public float getSegundosPasados() {
        return tticks/(float)FPS;
    }
    
    /** 
     * Numero de segundos restantes para finalizar el partido.
     * @return Segundos restante.
     */
    public float getSegundosRestantes() {
        return SEGS - tticks/(float)FPS;
    }
    
    /** 
     * Informacion del jugador n de mi equipo.
     * @param  n  El numero del jugador.
     * @return    El jugador elegido.
     */
    public Jugador getJugador(int n) {
        return jugs[0][n];
    }
    
    /** 
     * Informacion del jugador n del equipo e.
     * @param  n  El numero del jugador.
     * @param  e  El equipo.
     * @return    El jugador elegido.
     */
    public Jugador getJugador(int n, int e) {
        return jugs[e][n];
    }
    
    /**
     * Compruea si se trata del mismo jugador.
     * @param a  Un jugador.
     * @param b  Otro jugador.
     * @return   Verdadero si es el mismo jugador, Falso si son diferentes.
     */
    public boolean esElMismo(Jugador a, Jugador b) {
        if(a == null || b == null) return false;
        return (  a.e == b.e  &&  a.n == b.n  );
    }
    
    /**
     * Comprueba si un jugador tiene el balon.
     * @param j  Jugador que puede tener el balon.
     * @return   Verdadero si el jugador tiene el balon.
     */
    public boolean tieneBalon(Jugador j) {
        if(quien == null) return false;
        return (  j.e == quien.e  &&  j.n == quien.n  );
    }
    
    /** 
     * Copia los datos de un jugador en otro.
     * @param dest  Jugador copiado.
     * @param orig  Jugador original.
     */
    public void copyJugador(Jugador dest, Jugador orig) {
        dest.e = orig.e;
        dest.n = orig.n;
        dest.f = orig.f;
        dest.v = orig.v;
        dest.x = orig.x;
        dest.y = orig.y;
        dest.x0 = orig.x0;
        dest.y0 = orig.y0;
    }
    
    /** 
     * Duplicado de un jugador.
     * @param n  Numero del jugador.
     * @param e  Equipo del jugador.
     * @return   Una copia del jugador.
     */
    public Jugador getCopyJugador(int n, int e) {
        Jugador c = new Jugador();
        copyJugador(c, jugs[e][n]);
        return c;
    }
    
    /** 
     * Informacion de la bola del juego.
     * @return La bola del juego.
     */
    public Bola getBola() {
        return bola;
    }
    
    /** 
     * Numero de goles metidos por el equipo e.
     * @return Goles metidos por el equipo.
     */
    public int getGoles(int e) {
        return puntos[e];
    }
    
    /** 
     * Informacion de la permanencia de la bola.
     * @return Permanencia de la bola en el campo.
     */
    public long getDesempate() {
        return desempate;
    }
    
    /** 
     * Informacion de la permanencia de la bola dentro de f frames (si nadie la golpea de nuevo).
     * @return Permanencia de la bola en el campo.
     */
    public long getDesempate(int f) {
        if(f > TICKS-tticks) f = TICKS-tticks;
        return desempate + (bola.getX(f)/10)*f;
    }
    
    /** 
     * Para ver si mi equipo va ganando.
     * @return Verdadero si va ganando, False si pierde.
     */
    public boolean voyGanando() {
        if(puntos[0] > puntos[1]) return true;
        if(puntos[0] < puntos[1]) return false;
        if(desempate > 0) return true;
        if(desempate < 0) return false;
        return false;
    }
    
    /** 
     * Indica si el ultimo frame fue gol.
     * @return  No gol 0, gol mio +1, gol del contrario -1.
     */
    public int esGol() {
        return gol;
    }
    
    /**
     * Indica si en el ultimo frame el balon fue golpeado.
     */
    public boolean esGolpe() {
        return ( migolpe || golpe );
    }
    
    /**
     * Hay que llamar a este metodo cada vez que se intenta golpear la bola.
     * @param n  Numero del jugador que golpea.
     * @param x  Posicion X final de la bola.
     * @param y  Posicion Y final de la bola.
     * @return   Verdadero si puede golpear la bola.
     */
    public boolean setGolpe(int n, double x, double y) {        
        if(bola.v >= 15) return false;
        Jugador j = jugs[0][n];
        int bx = bola.getX();
        int by = bola.getY();
        double dis = (j.x-bx)*(j.x-bx) + (j.y-by)*(j.y-by);
        if(dis >= 10*10) return false;
        migolpeAng = Math.atan2(y-by, x-by);        
        migolpeVel = velocidadBolaSiGolpea(j, x, y);
        migolpe = true;
        return true;
    }
    
    /**
     * Indica si un jugador puede golpear el balon.
     * @param j  Jugador que quiere golpear el balon.
     * @return   Verdadero si el jugador puede golpear el balon.
     */
    public boolean puedeGolpear(Jugador j) {
        if(bola.v >= 15) return false;
        double dx = bola.getX() - j.x;
        double dy = bola.getY() - j.y;
        double dd = dx*dx + dy*dy;
        return ( dd < 10*10 );
    }
    
    /** 
     * Copia los datos de una bola en otra.
     * @param dest  Bola copiada.
     * @param orig  Bola original.
     */
    public static void copyBola(Bola dest, Bola orig) {
        dest.a = orig.a;
        dest.v = orig.v;
        for(int v = 0; v < 21; v++) {
            dest.xyd[v][0] = orig.xyd[v][0];
            dest.xyd[v][1] = orig.xyd[v][1];
            dest.xyd[v][2] = orig.xyd[v][2];
        }
    }
    
    /** 
     * Duplicado de la bola del juego.
     * @return Una copia de la bola del juego.
     */
    public Bola getCopyBola() {
        Bola copy = new Bola();
        copyBola(copy, bola);
        return copy;
    }

    /**
     * Calcula el punto golpeable de la trayectorioa de la bola mas cercano a un jugador.
     * @param j  Jugador de referencia.
     * @param b  Bola a golpear.
     * @param p  Valor de salida, el punto mas cercano.
     * @return   Distancia al punto.
     */
    public double getPuntoBolaCercano(Jugador j, Bola b, Point p) {
        double dx, dy, dd, min = 1e10;
        int v = b.v;
        if(v >= 15) v = 14;
        while(v >= 0) {
            dx = b.xyd[v][0] - j.x;
            dy = b.xyd[v][1] - j.y;
            dd = dx*dx + dy*dy;
            if(dd < min) {
                min = dd;
                p.x = b.xyd[v][0];
                p.y = b.xyd[v][1];
            }
            v--;
        }
        return Math.sqrt(min);
    }
    
    /**
     * Calcula el punto golpeable de la trayectorioa de la bola mas cercano a un jugador.
     * @param j  Jugador de referencia.
     * @param p  Valor de salida, el punto mas cercano.
     * @return   Distancia al punto.
     */
    public double getPuntoBolaCercano(Jugador j, Point p) {
        return getPuntoBolaCercano(j, bola, p);
    }
    
    /**
     * Calcula el numero de frames que faltan para que la bola pueda ser golpeada por el jugador j.
     * @param j  Jugador de referencia.
     * @param b  Bola a ser golpeada.
     * @return   Frames hasta golpear a la bola.
     */    
    public int getFrameGolpeBola(Jugador j, Bola b) {
        double dx, dy, dd, jd = 0;
        int v = b.v;
        int f = 0;
        while(v >= 0) {
            if(v < 15) {
                dx = b.xyd[v][0] - j.x;
                dy = b.xyd[v][1] - j.y;
                dd = dx*dx + dy*dy;
                if(dd < (jd+10)*(jd+10)) return f;
            }
            v--;
            f++;
            jd += j.v;
        }
        return 1000;
    }
    
    /**
     * Calcula el numero de frames que faltan para que la bola pueda ser golpeada por el jugador j.
     * @param j  Jugador de referencia.
     * @return   Frames hasta golpear a la bola.
     */    
    public int getFrameGolpeBola(Jugador j) {
        return getFrameGolpeBola(j, bola);
    }
    
    /**
     * Cuantos frames son necesarios para que algun jugador contraio puede llegar al punto XY.
     * @param x         Coordenada X del punto.
     * @param y         Coordenada Y del punto.
     * @param pxMargen  Margen de seguridad de las distancias en pixeles.
     * @param f         Frames pasados antes del inicio del calculo.
     * @return          Numero de frames hasta que algun jugador contrario llega a XY.
     */
    public int getFramesLibre(int x, int y, int pxMargen, int f) {
        if(bola.v-f >= 15) return 20;
        double dx, dy, d;
        for(int g = 0; g < 20; g++) {
            for(int n = 0; n < 11; n++) {
                dx = x-jugs[1][n].x;
                dy = y-jugs[1][n].y;
                d = Math.sqrt( dx*dx + dy*dy );
                d -= ( (g+f)*jugs[1][n].v + 10 ) + pxMargen;
                if(d <= 0.1) return g;
            }
        }
        return 20;
    }
    
    /**
     * Cuantos frames son necesarios para que algun jugador contraio puede llegar al punto XY.
     * @param x         Coordenada X del punto.
     * @param y         Coordenada Y del punto.
     * @param pxMargen  Margen de seguridad de las distancias en pixeles.
     * @return          Numero de frames hasta que algun jugador contrario llega a XY.
     */
    public int getFramesLibre(int x, int y, int pxMargen) {
        return getFramesLibre(x, y, pxMargen, 0);
    }
    
    /**
     * Indica que jugador tiene el balon.
     * @return Jugador con el balon o null si no lo tiene ninguno.
     */
    public Jugador quienTieneBalon() {
        return quien;
    }
    
   /** 
     * Valor de un angulo entre 0 y 2*PI.
     * @param ang  Angulo a normalizar/corregir.
     * @return   Angulo normalizado.
     */
    public static double normalizarAngulo(double ang) {
        final double dosPI = 2*Math.PI;
        while(ang <    0.0) ang += dosPI;
        while(ang >= dosPI) ang -= dosPI;
        return ang;
    }
    
    /**
     * Calcula la distancia entre dos jugadores.
     * @param j1  Primer jugador.
     * @param j2  Segundo jugador.
     * @return    Distancia entre los dos jugadores.
     */
    public static double distancia(Jugador j1, Jugador j2) {
        int dx = j2.x - j1.x;
        int dy = j2.y - j1.y;
        double dd = dx*dx + dy*dy;
        return Math.sqrt(dd);
    }
    
    /**
     * Calcula la distancia entre un jugador y la bola.
     * @param j   Jugador.
     * @param b   Bola.
     * @return    Distancia Jugador-Bola.
     */
    public static double distancia(Jugador j, Bola b) {
        int dx = b.getX() - j.x;
        int dy = b.getY() - j.y;
        double dd = dx*dx + dy*dy;
        return Math.sqrt(dd);
    }
    
    /**
     * Calcula la distancia entre un punto y la bola.
     * @param x   Coordenada X del punto.
     * @param y   Coordenada Y del punto.
     * @param b   Bola.
     * @return    Distancia Punto-Bola.
     */
    public static double distancia(double x, double y, Bola b) {
        double dx = b.getX() - x;
        double dy = b.getY() - y;
        double dd = dx*dx + dy*dy;
        return Math.sqrt(dd);
    }
    
    /** 
     * Calcular la velocidad de la bola si es golpeada.
     * @param j  Jugador que golpea la bola.
     * @param x  Coordenada X del destino de la bola.
     * @param y  Coordenada Y del destino de la bola.
     * @return   Velocidad que adquiere la bola.
     */
    public static int velocidadBolaSiGolpea(Jugador j, double x, double y) {        
        double dis = Math.sqrt( (j.x-x)*(j.x-x) + (j.y-y)*(j.y-y) );
        int grado = (int)(2*dis);
        if(grado > 100) grado = 100;
        return 3 * ( j.f*grado/100 );
    }
    
    /** 
     * Simulacion de la pelota golpeada por un jugador.
     * @param j  Jugador que golpea la bola.
     * @param x  Coordenada X del destino de la bola.
     * @param y  Coordenada Y del destino de la bola.
     * @param b  Valor de entrada y salida, la pelota golpeada.
     * @param f  Fuerza del golpe. Si es -1 se utiliza la fuerza original (la fuerza utilizada en el juego). Si es mayor que la fuerza del jugador se utiliza la fuerza maxima del jugador.
     * @return   Falso si no puede golpear el balon.
     */
    public static boolean simularGolpeBolaSinError(Jugador j, double x, double y, Bola b, int f) {
        if(b.v >= 15) return false;
        int bx = b.getX();
        int by = b.getY();
        double dis = (j.x-bx)*(j.x-bx) + (j.y-by)*(j.y-by);
        if(dis >= 10*10) return false;
        b.a = Math.atan2(y-by, x-by);
        if      (f < 0)    b.v = velocidadBolaSiGolpea(j, x, y);
        else if (f <= j.f) b.v = 3*f;
        else               b.v = 3*j.f;
        calculosBolaPosiciones(b.a, b.v, bx, by, b.xyd);        
        return true;
    }

    /** 
     * Predice la posicion de destino de un jugador.
     * @param    j  Jugador al que investigar.
     * @param    f  Despues de cuantos frames.
     * @param dest  Valor de salida, el destino.
     */
    public static void jugadorDondeVa(Jugador j, int f, Point dest) {
        double dx = j.x - j.x0;
        double dy = j.y - j.y0;
        double t = dx*dx + dy*dy;
        if(t < 0.1) return;
        t = f * j.v / Math.sqrt(t);
        dest.x = j.x + (int)(t*dx);
        dest.y = j.y + (int)(t*dy);
    }
    
    
    /** 
     * Actualiza los datos del estado del juego.
     * @param sj Posicion de los jugadores.
     */
    public void calculos(SituacionJugadores sj) {
        calculosGol(sj.getBola());
        calculosBola(sj.getBola());
        calculosEquipo(0, sj.getMisJugadores());
        calculosEquipo(1, sj.getContrario());
        calculosQuienTieneBalon();
        desempate += bola.getX()/10;
        tticks++;
        gticks++;
    }

    private void calculosGol(Point p) {
        gol = 0;
        int dx = bola.getX() - p.x;
        if(dx > +30) {
            gol = +1;
            puntos[0]++;
            gticks = -1;
        }
        if(dx < -30) {
            gol = -1;
            puntos[1]++;
            gticks = -1;
        }
    }
    
    private void calculosBola(Point p) {
        if(gol != 0) {
            bola.v = 0;
            bola.q = 0;
            bola.xyd[0][0] = 0;
            bola.xyd[0][1] = 0;
            bola.xyd[0][2] = 0;
            bola.a = Math.PI;
            bola.dx = 0.0;
            bola.dy = 0.0;
            golpe = migolpe = false;
        } else {
            double x = bola.xyd[bola.v][0];
            double y = bola.xyd[bola.v][1];
            double a = Math.atan2(p.y-y, p.x-x);
            migolpeVel--;
            migolpe = migolpe && (migolpeVel >= 15) && ( (a-migolpeAng)*(a-migolpeAng) < 0.1 );
            int v = (int) ( migolpe ? migolpeVel : Math.round( p.distance(x, y) )-1 );
            if(v > 21) v = 21;
            if(v < 0)  v =  0;
            golpe = (  v > 2  &&  ( (bola.v-v)*(bola.v-v) > 4  ||  (a-bola.a)*(a-bola.a) > 0.1 ) &&  quienTieneBalon() != null  );
            bola.a = a;
            bola.v = v;
            bola.q = ( v > 0 ? 0 : bola.q+1 );
            bola.dx = Math.cos( bola.a );
            bola.dy = Math.sin( bola.a );
            calculosBolaPosiciones(a, v, p.x, p.y, bola.xyd);
        }
    }
    
    private static void calculosBolaPosiciones(double a, int v, double x, double y, int[][] xyd) {
        double c = Math.cos(a);
        double s = Math.sin(a);
        xyd[v][0] = (int)x;
        xyd[v][1] = (int)y;
        xyd[v][2] = 0;
        boolean giro = false;
        while(v > 0) {
            for(int n = 0; n < v; n++) {
                x += c;
                y += s;
                if(x > +160) {  x = +320-x;  a = Math.PI-a;  giro = true;  }
                if(x < -160) {  x = -320-x;  a = Math.PI-a;  giro = true;  }
                if(y > +120) {  y = +240-y;  a = -a;         giro = true;  }
                if(y < -120) {  y = -240-y;  a = -a;         giro = true;  }
                if(giro) {
                    a = normalizarAngulo(a);
                    c = Math.cos(a);
                    s = Math.sin(a);
                    giro = false;
                }
            }
            v--;
            xyd[v][0] = (int)x;
            xyd[v][1] = (int)y;
            xyd[v][2] = xyd[v+1][2] + v;
        }
    }
    
    private void calculosEquipo(int e, Point[] pos) {
        Jugador j = null;
        if(gol != 0) {
            for(int n = 0; n < 11; n++) {
                j = jugs[e][n];
                j.x = j.x0 = pos[n].x;
                j.y = j.y0 = pos[n].y;   
            }            
        } else {
            for(int n = 0; n < 11; n++) {
                j = jugs[e][n];
                j.x0 = j.x;
                j.y0 = j.y;
                j.x = pos[n].x;
                j.y = pos[n].y;   
            }
            if(e == 1) {
                double dd = 0;
                for(int n = 0; n < 11; n++) {
                    j = jugs[e][n];
                    dd = (j.x-j.x0)*(j.x-j.x0) + (j.y-j.y0)*(j.y-j.y0);
                    if(dd > j.v*j.v) {
                        j.v = (int) Math.round( Math.sqrt(dd) );
                        if(j.v < 3) j.v = 3;
                        if(j.v > 6) j.v = 6;
                        j.f = 10 - j.v;
                    }
                }
            }
        }
    }
    
    private void calculosQuienTieneBalon() {
        if(bola.v >= 15) quien = null;
        int x = bola.getX();
        int y = bola.getY();
        double dx, dy;
        quien = null;
        for(int e = 1; e >= 0; e--) {
            for(int n = 0; n < 11; n++) {
                dx = x-jugs[e][n].x;
                dy = y-jugs[e][n].y;
                if( dx*dx + dy*dy < 10*10 ) {
                    quien = jugs[e][n];
                    return;
                }
            }
        }
    }
    
}
